const electron = require('electron');
const updater = require('electron-updater');
const fs = require('fs');
const os = require('os');
const path = require('path');
const process = require('process');
const url = require('url');

class Application {
  constructor() {
    this._views = new ViewCollection();
    this._configuration = new ConfigurationService();
    this._menu = new MenuService();
    this._openQueue = [];

    electron.app.setAppUserModelId('com.lutzroeder.netron');
    electron.app.allowRendererProcessReuse = true;

    if (!electron.app.requestSingleInstanceLock()) {
      electron.app.quit();
      return;
    }

    electron.app.on('second-instance', (event, commandLine, workingDirectory) => {
      const currentDirectory = process.cwd();
      process.chdir(workingDirectory);
      const open = this._parseCommandLine(commandLine);
      process.chdir(currentDirectory);
      if (!open) {
        if (this._views.count > 0) {
          const view = this._views.item(0);
          if (view) {
            view.restore();
          }
        }
      }
    });

    electron.ipcMain.on('open-file-dialog', () => {
      this._openFileDialog();
    });

    electron.ipcMain.on('get-environment', (event) => {
      event.returnValue = {
        version: electron.app.getVersion(),
        package: electron.app.isPackaged,
      };
    });
    electron.ipcMain.on('get-configuration', (event, obj) => {
      event.returnValue = this._configuration.has(obj.name) ? this._configuration.get(obj.name) : undefined;
    });
    electron.ipcMain.on('set-configuration', (event, obj) => {
      this._configuration.set(obj.name, obj.value);
    });
    electron.ipcMain.on('drop-paths', (event, data) => {
      const paths = data.paths.filter((path) => {
        if (fs.existsSync(path)) {
          const stat = fs.statSync(path);
          return stat.isFile() || stat.isDirectory();
        }
        return false;
      });
      this._dropPaths(event.sender, paths);
    });
    electron.ipcMain.on('show-message-box', (event, options) => {
      const owner = event.sender.getOwnerBrowserWindow();
      event.returnValue = electron.dialog.showMessageBoxSync(owner, options);
    });
    electron.ipcMain.on('show-save-dialog', (event, options) => {
      const owner = event.sender.getOwnerBrowserWindow();
      event.returnValue = electron.dialog.showSaveDialogSync(owner, options);
    });

    electron.app.on('will-finish-launching', () => {
      electron.app.on('open-file', (event, path) => {
        this._openPath(path);
      });
    });

    electron.app.on('ready', () => {
      this._ready();
    });

    electron.app.on('window-all-closed', () => {
      if (process.platform !== 'darwin') {
        electron.app.quit();
      }
    });

    electron.app.on('will-quit', () => {
      this._configuration.save();
    });

    this._parseCommandLine(process.argv);
    this._checkForUpdates();
  }

  _parseCommandLine(argv) {
    let open = false;
    if (argv.length > 1) {
      for (const arg of argv.slice(1)) {
        if (!arg.startsWith('-') && arg !== path.dirname(__dirname)) {
          const extension = path.extname(arg).toLowerCase();
          if (extension != '' && extension != 'js' && fs.existsSync(arg)) {
            const stat = fs.statSync(arg);
            if (stat.isFile() || stat.isDirectory()) {
              this._openPath(arg);
              open = true;
            }
          }
        }
      }
    }
    return open;
  }

  _ready() {
    this._configuration.load();
    if (!this._configuration.has('userId')) {
      this._configuration.set('userId', this._uuid());
    }
    if (this._openQueue) {
      const queue = this._openQueue;
      this._openQueue = null;
      while (queue.length > 0) {
        const file = queue.shift();
        this._openPath(file);
      }
    }
    if (this._views.count == 0) {
      this._views.openView();
    }
    this._resetMenu();
    this._views.on('active-view-changed', () => {
      this._updateMenu();
    });
    this._views.on('active-view-updated', () => {
      this._updateMenu();
    });
  }

  _uuid() {
    const buffer = new Uint8Array(16);
    require('crypto').randomFillSync(buffer);
    buffer[6] = (buffer[6] & 0x0f) | 0x40;
    buffer[8] = (buffer[8] & 0x3f) | 0x80;
    const code = Array.from(buffer)
      .map((value) => (value < 0x10 ? '0' + value.toString(16) : value.toString(16)))
      .join('');
    return (
      code.slice(0, 8) +
      '-' +
      code.slice(8, 12) +
      '-' +
      code.slice(12, 16) +
      '-' +
      code.slice(16, 20) +
      '-' +
      code.slice(20, 32)
    );
  }

  _openFileDialog() {
    const showOpenDialogOptions = {
      properties: ['openFile'],
      filters: [
        {
          name: 'All Model Files',
          extensions: [
            'onnx',
            'onn',
            'pb',
            'onnxtxt',
            'pbtxt',
            'prototxt',
            'txt',
            'model',
            'pt',
            'pth',
            'pkl',
            'ort',
            'ort.onnx',
          ],
        },
      ],
    };
    const selectedFiles = electron.dialog.showOpenDialogSync(showOpenDialogOptions);
    if (selectedFiles) {
      for (const file of selectedFiles) {
        this._openPath(file);
      }
    }
  }

  _openPath(path) {
    if (this._openQueue) {
      this._openQueue.push(path);
      return;
    }
    if (path && path.length > 0 && fs.existsSync(path)) {
      const stat = fs.statSync(path);
      if (stat.isFile() || stat.isDirectory()) {
        // find existing view for this file
        let view = this._views.find(path);
        // find empty welcome window
        if (view == null) {
          view = this._views.find(null);
        }
        // create new window
        if (view == null) {
          view = this._views.openView();
        }
        this._loadPath(path, view);
      }
    }
  }

  _loadPath(path, view) {
    const recents = this._configuration.get('recents').filter((recent) => path != recent.path);
    view.open(path);
    recents.unshift({ path: path });
    if (recents.length > 9) {
      recents.splice(9);
    }
    this._configuration.set('recents', recents);
    this._resetMenu();
  }

  _dropPaths(sender, paths) {
    let view = this._views.from(sender);
    for (const path of paths) {
      if (view) {
        this._loadPath(path, view);
        view = null;
      } else {
        this._openPath(path);
      }
    }
  }

  _export() {
    const view = this._views.activeView;
    if (view && view.path) {
      let defaultPath = 'Untitled';
      const file = view.path;
      const lastIndex = file.lastIndexOf('.');
      if (lastIndex != -1) {
        defaultPath = file.substring(0, lastIndex);
      }
      const owner = electron.BrowserWindow.getFocusedWindow();
      const showSaveDialogOptions = {
        title: 'Export',
        defaultPath: defaultPath,
        buttonLabel: 'Export',
        filters: [
          { name: 'PNG', extensions: ['png'] },
          { name: 'SVG', extensions: ['svg'] },
        ],
      };
      const selectedFile = electron.dialog.showSaveDialogSync(owner, showSaveDialogOptions);
      if (selectedFile) {
        view.execute('export', { file: selectedFile });
      }
    }
  }

  service(name) {
    if (name == 'configuration') {
      return this._configuration;
    }
    return undefined;
  }

  execute(command, data) {
    const view = this._views.activeView;
    if (view) {
      view.execute(command, data || {});
    }
    this._updateMenu();
  }

  _reload() {
    const view = this._views.activeView;
    if (view && view.path) {
      this._loadPath(view.path, view);
    }
  }

  _checkForUpdates() {
    if (!electron.app.isPackaged) {
      return;
    }
    const autoUpdater = updater.autoUpdater;
    if (autoUpdater.app && autoUpdater.app.appUpdateConfigPath && !fs.existsSync(autoUpdater.app.appUpdateConfigPath)) {
      return;
    }
    const promise = autoUpdater.checkForUpdates();
    if (promise) {
      promise.catch((error) => {
        /* eslint-disable */
        console.log(error.message);
        /* eslint-enable */
      });
    }
  }

  get package() {
    if (!this._package) {
      const file = path.join(path.dirname(__dirname), 'package.json');
      const data = fs.readFileSync(file);
      this._package = JSON.parse(data);
      this._package.date = new Date(fs.statSync(file).mtime);
    }
    return this._package;
  }

  _about() {
    let dialog = null;
    const options = {
      show: false,
      backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#2d2d2d' : '#e6e6e6',
      width: 400,
      height: 250,
      center: true,
      minimizable: false,
      maximizable: false,
      useContentSize: true,
      resizable: true,
      fullscreenable: false,
      webPreferences: {
        nodeIntegration: true,
      },
    };
    if (process.platform === 'darwin') {
      options.title = '';
      dialog = Application._aboutDialog;
    } else {
      options.title = 'About ' + electron.app.name;
      options.parent = electron.BrowserWindow.getFocusedWindow();
      options.modal = true;
      options.showInTaskbar = false;
    }
    if (process.platform === 'win32') {
      options.type = 'toolbar';
    }
    if (!dialog) {
      dialog = new electron.BrowserWindow(options);
      if (process.platform === 'darwin') {
        Application._aboutDialog = dialog;
      }
      dialog.removeMenu();
      dialog.excludedFromShownWindowsMenu = true;
      dialog.webContents.on('new-window', (event, url) => {
        if (url.startsWith('http://') || url.startsWith('https://')) {
          event.preventDefault();
          electron.shell.openExternal(url);
        }
      });
      let content = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8');
      content = content.replace('{version}', this.package.version);
      content = content.replace('<title>Netron</title>', '');
      content = content.replace('<body class="welcome spinner">', '<body class="about desktop">');
      content = content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
      content = content.replace(/<link.*>/gi, '');
      dialog.once('ready-to-show', () => {
        dialog.resizable = false;
        dialog.show();
      });
      dialog.on('close', function () {
        electron.globalShortcut.unregister('Escape');
        Application._aboutDialog = null;
      });
      dialog.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(content));
      electron.globalShortcut.register('Escape', function () {
        dialog.close();
      });
    } else {
      dialog.show();
    }
  }

  _updateMenu() {
    const window = electron.BrowserWindow.getFocusedWindow();
    this._menu.update(
      {
        window: window,
        webContents: window ? window.webContents : null,
        view: this._views.activeView,
      },
      this._views.views.map((view) => view.window)
    );
  }

  _resetMenu() {
    const menuRecentsTemplate = [];
    if (this._configuration.has('recents')) {
      let recents = this._configuration.get('recents');
      recents = recents.filter((recent) => {
        const path = recent.path;
        if (fs.existsSync(path)) {
          const stat = fs.statSync(path);
          if (stat.isFile() || stat.isDirectory()) {
            return true;
          }
        }
        return false;
      });
      if (recents.length > 9) {
        recents.splice(9);
      }
      this._configuration.set('recents', recents);
      for (let i = 0; i < recents.length; i++) {
        const recent = recents[i];
        menuRecentsTemplate.push({
          path: recent.path,
          label: Application.minimizePath(recent.path),
          accelerator: (process.platform === 'darwin' ? 'Cmd+' : 'Ctrl+') + (i + 1).toString(),
          click: (item) => {
            this._openPath(item.path);
          },
        });
      }
    }

    const menuTemplate = [];

    if (process.platform === 'darwin') {
      menuTemplate.unshift({
        label: electron.app.name,
        submenu: [
          {
            label: 'About ' + electron.app.name,
            click: () => this._about(),
          },
          { type: 'separator' },
          { role: 'hide' },
          { role: 'hideothers' },
          { role: 'unhide' },
          { type: 'separator' },
          { role: 'quit' },
        ],
      });
    }

    menuTemplate.push({
      label: '&File',
      submenu: [
        {
          label: '&Open...',
          accelerator: 'CmdOrCtrl+O',
          click: () => {
            this._openFileDialog();
          },
        },
        {
          label: 'Open &Recent',
          submenu: menuRecentsTemplate,
        },
        { type: 'separator' },
        {
          id: 'file.export',
          label: '&Export...',
          accelerator: 'CmdOrCtrl+Shift+E',
          click: () => this._export(),
        },
        { type: 'separator' },
        { role: 'close' },
      ],
    });

    if (process.platform !== 'darwin') {
      menuTemplate.slice(-1)[0].submenu.push({ type: 'separator' }, { role: 'quit' });
    }

    if (process.platform == 'darwin') {
      electron.systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true);
      electron.systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', true);
    }

    menuTemplate.push({
      label: '&Edit',
      submenu: [
        {
          id: 'edit.cut',
          label: 'Cu&t',
          accelerator: 'CmdOrCtrl+X',
          click: () => this.execute('cut', null),
        },
        {
          id: 'edit.copy',
          label: '&Copy',
          accelerator: 'CmdOrCtrl+C',
          click: () => this.execute('copy', null),
        },
        {
          id: 'edit.paste',
          label: '&Paste',
          accelerator: 'CmdOrCtrl+V',
          click: () => this.execute('paste', null),
        },
        {
          id: 'edit.select-all',
          label: 'Select &All',
          accelerator: 'CmdOrCtrl+A',
          click: () => this.execute('selectall', null),
        },
        { type: 'separator' },
        {
          id: 'edit.find',
          label: '&Find...',
          accelerator: 'CmdOrCtrl+F',
          click: () => this.execute('find', null),
        },
      ],
    });

    const viewTemplate = {
      label: '&View',
      submenu: [
        {
          id: 'view.toggle-attributes',
          accelerator: 'CmdOrCtrl+D',
          click: () => this.execute('toggle', 'attributes'),
        },
        {
          id: 'view.toggle-initializers',
          accelerator: 'CmdOrCtrl+I',
          click: () => this.execute('toggle', 'initializers'),
        },
        {
          id: 'view.toggle-names',
          accelerator: 'CmdOrCtrl+U',
          click: () => this.execute('toggle', 'names'),
        },
        {
          id: 'view.toggle-direction',
          accelerator: 'CmdOrCtrl+K',
          click: () => {
            this.execute('toggle', 'direction');
          },
        },
        {
          id: 'view.toggle-mousewheel',
          accelerator: 'CmdOrCtrl+M',
          click: () => this.execute('toggle', 'mousewheel'),
        },
        { type: 'separator' },
        {
          id: 'view.reload',
          label: '&Reload',
          accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'F5',
          click: () => this._reload(),
        },
        { type: 'separator' },
        {
          id: 'view.reset-zoom',
          label: 'Actual &Size',
          accelerator: 'Shift+Backspace',
          click: () => this.execute('reset-zoom', null),
        },
        {
          id: 'view.zoom-in',
          label: 'Zoom &In',
          accelerator: 'Shift+Up',
          click: () => this.execute('zoom-in', null),
        },
        {
          id: 'view.zoom-out',
          label: 'Zoom &Out',
          accelerator: 'Shift+Down',
          click: () => this.execute('zoom-out', null),
        },
        { type: 'separator' },
        {
          id: 'view.show-properties',
          label: '&Properties...',
          accelerator: 'CmdOrCtrl+Enter',
          click: () => this.execute('show-properties', null),
        },
      ],
    };
    if (!electron.app.isPackaged) {
      viewTemplate.submenu.push({ type: 'separator' });
      viewTemplate.submenu.push({ role: 'toggledevtools' });
    }
    menuTemplate.push(viewTemplate);

    if (process.platform === 'darwin') {
      menuTemplate.push({
        role: 'window',
        submenu: [{ role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }],
      });
    }

    const helpSubmenu = [];

    if (process.platform != 'darwin') {
      helpSubmenu.push({ type: 'separator' });
      helpSubmenu.push({
        label: 'About ' + electron.app.name,
        click: () => this._about(),
      });
    }

    menuTemplate.push({
      role: 'help',
      submenu: helpSubmenu,
    });

    const commandTable = new Map();
    commandTable.set('file.export', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('edit.cut', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('edit.copy', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('edit.paste', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('edit.select-all', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('edit.find', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('view.toggle-attributes', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
      label: (context) => {
        return !context.view || !context.view.get('attributes') ? 'Hide &Attributes' : 'Show &Attributes';
      },
    });
    commandTable.set('view.toggle-initializers', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
      label: (context) => {
        return !context.view || context.view.get('initializers') ? 'Hide &Initializers' : 'Show &Initializers';
      },
    });
    commandTable.set('view.toggle-names', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
      label: (context) => {
        return !context.view || context.view.get('names') ? 'Hide &Names' : 'Show &Names';
      },
    });
    commandTable.set('view.toggle-direction', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
      label: (context) => {
        return !context.view || context.view.get('direction') === 'vertical' ? 'Show &Horizontal' : 'Show &Vertical';
      },
    });
    commandTable.set('view.toggle-mousewheel', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
      label: (context) => {
        return !context.view || context.view.get('mousewheel') === 'scroll'
          ? '&Mouse Wheel: Zoom'
          : '&Mouse Wheel: Scroll';
      },
    });
    commandTable.set('view.reload', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('view.reset-zoom', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('view.zoom-in', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('view.zoom-out', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });
    commandTable.set('view.show-properties', {
      enabled: (context) => {
        return context.view && context.view.path ? true : false;
      },
    });

    this._menu.build(
      menuTemplate,
      commandTable,
      this._views.views.map((view) => view.window)
    );
    this._updateMenu();
  }

  static minimizePath(file) {
    if (process.platform != 'win32') {
      const homeDir = os.homedir();
      if (file.startsWith(homeDir)) {
        return '~' + file.substring(homeDir.length);
      }
    }
    return file;
  }
}

class View {
  constructor(owner) {
    this._owner = owner;
    this._ready = false;
    this._path = null;
    this._properties = new Map();
    this._location = url.format({ protocol: 'file:', slashes: true, pathname: path.join(__dirname, 'index.html') });

    const size = electron.screen.getPrimaryDisplay().workAreaSize;
    const options = {
      show: false,
      title: electron.app.name,
      backgroundColor: electron.nativeTheme.shouldUseDarkColors ? '#1d1d1d' : '#e6e6e6',
      icon: electron.nativeImage.createFromPath(path.join(__dirname, 'icon.png')),
      minWidth: 600,
      minHeight: 400,
      width: size.width > 1024 ? 1024 : size.width,
      height: size.height > 768 ? 768 : size.height,
      webPreferences: {
        preload: path.join(__dirname, 'electron.js'),
        nodeIntegration: true,
      },
    };
    if (this._owner.count > 0 && View._position && View._position.length == 2) {
      options.x = View._position[0] + 30;
      options.y = View._position[1] + 30;
      if (options.x + options.width > size.width) {
        options.x = 0;
      }
      if (options.y + options.height > size.height) {
        options.y = 0;
      }
    }
    this._window = new electron.BrowserWindow(options);
    View._position = this._window.getPosition();
    this._updateCallback = (event, data) => {
      if (event.sender == this._window.webContents) {
        for (const entry of Object.entries(data)) {
          this.update(entry[0], entry[1]);
        }
        this._raise('updated');
      }
    };
    electron.ipcMain.on('update', this._updateCallback);
    this._window.on('closed', () => {
      electron.ipcMain.removeListener('update', this._updateCallback);
      this._owner.closeView(this);
    });
    this._window.on('focus', () => {
      this._raise('activated');
    });
    this._window.on('blur', () => {
      this._raise('deactivated');
    });
    this._window.webContents.on('did-finish-load', () => {
      this._didFinishLoad = true;
    });
    this._window.webContents.on('new-window', (event, url) => {
      if (url.startsWith('http://') || url.startsWith('https://')) {
        event.preventDefault();
        electron.shell.openExternal(url);
      }
    });
    this._window.once('ready-to-show', () => {
      this._window.show();
    });
    this._window.loadURL(this._location);
  }

  get window() {
    return this._window;
  }

  get path() {
    return this._path;
  }

  open(path) {
    this._openPath = path;
    if (this._didFinishLoad) {
      this._window.webContents.send('open', { path: path });
    } else {
      this._window.webContents.on('did-finish-load', () => {
        this._window.webContents.send('open', { path: path });
      });
      this._window.loadURL(this._location);
    }
  }

  restore() {
    if (this._window) {
      if (this._window.isMinimized()) {
        this._window.restore();
      }
      this._window.show();
    }
  }

  match(path) {
    if (this._openPath) {
      if (path === null) {
        return false;
      }
      if (path === this._openPath) {
        return true;
      }
    }
    return this._path == path;
  }

  execute(command, data) {
    if (this._window && this._window.webContents) {
      this._window.webContents.send(command, data);
    }
  }

  update(name, value) {
    if (name === 'path') {
      if (value) {
        this._path = value;
        const title = Application.minimizePath(this._path);
        this._window.setTitle(process.platform !== 'darwin' ? title + ' - ' + electron.app.name : title);
        this._window.focus();
      }
      this._openPath = null;
      return;
    }
    this._properties.set(name, value);
  }

  get(name) {
    return this._properties.get(name);
  }

  on(event, callback) {
    this._events = this._events || {};
    this._events[event] = this._events[event] || [];
    this._events[event].push(callback);
  }

  _raise(event, data) {
    if (this._events && this._events[event]) {
      for (const callback of this._events[event]) {
        callback(this, data);
      }
    }
  }
}

class ViewCollection {
  constructor() {
    this._views = [];
  }

  get views() {
    return this._views;
  }

  get count() {
    return this._views.length;
  }

  item(index) {
    return this._views[index];
  }

  openView() {
    const view = new View(this);
    view.on('activated', (sender) => {
      this._activeView = sender;
      this._raise('active-view-changed', { activeView: this._activeView });
    });
    view.on('updated', () => {
      this._raise('active-view-updated', { activeView: this._activeView });
    });
    view.on('deactivated', () => {
      this._activeView = null;
      this._raise('active-view-changed', { activeView: this._activeView });
    });
    this._views.push(view);
    this._updateActiveView();
    return view;
  }

  closeView(view) {
    for (let i = this._views.length - 1; i >= 0; i--) {
      if (this._views[i] == view) {
        this._views.splice(i, 1);
      }
    }
    this._updateActiveView();
  }

  find(path) {
    return this._views.find((view) => view.match(path));
  }

  from(contents) {
    return this._views.find(
      (view) => view && view.window && view.window.webContents && view.window.webContents == contents
    );
  }

  get activeView() {
    return this._activeView;
  }

  on(event, callback) {
    this._events = this._events || {};
    this._events[event] = this._events[event] || [];
    this._events[event].push(callback);
  }

  _raise(event, data) {
    if (this._events && this._events[event]) {
      for (const callback of this._events[event]) {
        callback(this, data);
      }
    }
  }

  _updateActiveView() {
    const window = electron.BrowserWindow.getFocusedWindow();
    const view = this._views.find((view) => view.window == window) || null;
    if (view != this._activeView) {
      this._activeView = view;
      this._raise('active-view-changed', { activeView: this._activeView });
    }
  }
}

class ConfigurationService {
  load() {
    this._data = { recents: [] };
    const dir = electron.app.getPath('userData');
    if (dir && dir.length > 0) {
      const file = path.join(dir, 'configuration.json');
      if (fs.existsSync(file)) {
        const data = fs.readFileSync(file);
        if (data) {
          try {
            this._data = JSON.parse(data);
          } catch (error) {
            // continue regardless of error
          }
        }
      }
    }
  }

  save() {
    if (this._data) {
      const data = JSON.stringify(this._data, null, 2);
      if (data) {
        const dir = electron.app.getPath('userData');
        if (dir && dir.length > 0) {
          const file = path.join(dir, 'configuration.json');
          fs.writeFileSync(file, data);
        }
      }
    }
  }

  has(name) {
    return this._data && Object.prototype.hasOwnProperty.call(this._data, name);
  }

  set(name, value) {
    this._data[name] = value;
  }

  get(name) {
    return this._data[name];
  }
}

class MenuService {
  build(menuTemplate, commandTable, windows) {
    this._menuTemplate = menuTemplate;
    this._commandTable = commandTable;
    this._itemTable = new Map();
    for (const menu of menuTemplate) {
      for (const item of menu.submenu) {
        if (item.id) {
          if (!item.label) {
            item.label = '';
          }
          this._itemTable.set(item.id, item);
        }
      }
    }
    this._rebuild(windows);
  }

  update(context, windows) {
    if (!this._menu && !this._commandTable) {
      return;
    }
    if (this._updateLabel(context)) {
      this._rebuild(windows);
    }
    this._updateEnabled(context);
  }

  _rebuild(windows) {
    this._menu = electron.Menu.buildFromTemplate(this._menuTemplate);
    if (process.platform === 'darwin') {
      electron.Menu.setApplicationMenu(this._menu);
    } else {
      for (const window of windows) {
        window.setMenu(this._menu);
      }
    }
  }

  _updateLabel(context) {
    let rebuild = false;
    for (const entry of this._commandTable.entries()) {
      const menuItem = this._menu.getMenuItemById(entry[0]);
      const command = entry[1];
      if (command && command.label) {
        const label = command.label(context);
        if (label != menuItem.label) {
          if (this._itemTable.has(entry[0])) {
            this._itemTable.get(entry[0]).label = label;
            rebuild = true;
          }
        }
      }
    }
    return rebuild;
  }

  _updateEnabled(context) {
    for (const entry of this._commandTable.entries()) {
      const menuItem = this._menu.getMenuItemById(entry[0]);
      if (menuItem) {
        const command = entry[1];
        if (command.enabled) {
          menuItem.enabled = command.enabled(context);
        }
      }
    }
  }
}

global.application = new Application();
